package com.dwork.base.validate.tag;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;

import javax.validation.constraints.AssertFalse;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.DecimalMax;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.Digits;
import javax.validation.constraints.Future;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Null;
import javax.validation.constraints.Past;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;

import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotBlank;
import org.hibernate.validator.constraints.NotEmpty;
import org.hibernate.validator.constraints.Range;
import org.hibernate.validator.constraints.URL;
import org.springframework.context.MessageSource;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.web.context.ContextLoader;

import com.dwork.base.utils.StringUtil;
import com.dwork.base.validate.checkevent.ChangeListener;
import com.dwork.base.validate.checkevent.Click;
import com.dwork.base.validate.checkevent.Focusout;
import com.dwork.base.validate.checkevent.Keyup;




/**
 * 基于标注配置的前端实体验证JS生成器
 * @author rainbow
 *
 */
public class EntityValidateJSFactory {
	private static final Class[] validateAnnotations = {
			AssertFalse.class,
			AssertTrue.class,
			DecimalMax.class,
			DecimalMin.class,
			Digits.class,
			Future.class,
			Max.class,
			Min.class,
			NotNull.class,
			Null.class,
			Past.class,
			Pattern.class,
			Size.class,
			
			NotBlank.class,
			NotEmpty.class,
			Length.class,
			Email.class,
			Range.class,
			URL.class
	};
	
	private static final List<String> validateAnnotationList;
	
	private static MessageSource messageSource;
	/*初使化messageSource以及支持的检证标签名列表*/
	static {
		validateAnnotationList = new ArrayList<String>();
		for(Class c:validateAnnotations) {
			validateAnnotationList.add(c.getName());
		}
		try {
			messageSource = (MessageSource) ContextLoader.getCurrentWebApplicationContext().getBean("messageSource");
		} catch(Exception e) {
			e.printStackTrace();
			messageSource = new ClassPathXmlApplicationContext("applicationContext.xml");
		}
	}
	/**
	 * 实体类全名
	 */
	private String entityFullName;
	/**
	 * 实体例
	 */
	private Class clazz;
	
	private String formSelector;
	
	private String groupName;
	
	private String showErrorsFunctionName;
	
	private Map<Field, List<Annotation>> validateMap = new HashMap<Field, List<Annotation>>();
	
	private List<String> focusoutList = new ArrayList<String>();
	
	private List<String> keyupList = new ArrayList<String>();
	
	private List<String> clickList = new ArrayList<String>();
	/**
	 * 默认检查项
	 */
	private String defaultCheck = "focusout";
	
	String validateStr = null;
	/**
	 * 构造方法
	 * @param formSelector 将生成的验证JS方法配置绑定到指定的JQUERY选择器上。
	 * @param showErrorsFunctionName 前端显示验证信息的回调方法名
	 * @param entityFullName  所需验证的实体完整类名
	 * @param groups    所需验证的组别接口全名
	 * @throws Exception 
	 */
	public EntityValidateJSFactory(String formSelector,String showErrorsFunctionName, String entityFullName, String groupName) throws Exception{
		this.formSelector 	= formSelector;
		this.showErrorsFunctionName = showErrorsFunctionName;
		this.entityFullName = entityFullName;
		if(groupName==null||"".equals(groupName)) {
			groupName = "com.dwork.base.validate.groups.DEFAULT";
		}
		this.groupName		= groupName;
		config();
	}
	/**
	 * 装配
	 * @throws Exception
	 */
	private void config() throws Exception {
		clazz = Class.forName(entityFullName);
		Field[] fields = clazz.getDeclaredFields();
		for(int i=0;i<fields.length;i++) {
			Field f = fields[i];
			Annotation[] annotations = f.getAnnotations();
			for(int j=0;j<annotations.length;j++) {
				Annotation annotation = annotations[j];
				if(validateAnnotationList.contains(annotation.annotationType().getName())) {
					if(validateMap.get(f)==null) {
						List<Annotation> annotationList = new ArrayList<Annotation>();
						annotationList.add(annotation);
						validateMap.put(f, annotationList);
					} else {
						List<Annotation> annotationList = validateMap.get(f);
						annotationList.add(annotation);
					}
				}
				if(annotation.annotationType().getName().equals(Click.class.getName())) {
					clickList.add(f.getName());
				} else if(annotation.annotationType().getName().equals(Focusout.class.getName())) {
					focusoutList.add(f.getName());
				} else if(annotation.annotationType().getName().equals(Keyup.class.getName())) {
					keyupList.add(f.getName());
				}
			}
		}
	}
	/**
	 * 生产JS
	 * @return
	 */
	public String productJS(){
		if(validateStr!=null) {
			return validateStr;
		}
		StringBuffer validate = 
		new StringBuffer("$(\""+formSelector+"\").validate({\n");	
		validate.append("\t showErrors: function(errorMap,errorList){\n");
		validate.append("\t 	"+showErrorsFunctionName+"(errorList); \n");
		validate.append("\t }, \n");
		validate.append("\t defaultCheck:\""+defaultCheck+"\",\n");
		if(keyupList.size()>0) {
			validate.append("\tkeyupGroup:["+StringUtil.join(keyupList.toArray(), "\"", "\"", ",")+"],\n");
		}
		if(clickList.size()>0) {
			validate.append("\tclickGroup:["+StringUtil.join(clickList.toArray(), "\"", "\"", ",")+"],\n");
		}
		if(focusoutList.size()>0) {
			validate.append("\tfocusoutGroup:["+StringUtil.join(focusoutList.toArray(), "\"", "\"", ",")+"],\n");
		}
		
		StringBuffer rules = new StringBuffer("\trules: {\n");
		StringBuffer messages = new StringBuffer("\tmessages: {\n");
		
		
		
		if(!validateMap.isEmpty()) {
			Iterator<Field> it = validateMap.keySet().iterator();
			while(it.hasNext()) {
				Field field = it.next();
				List<Annotation> annotationList = validateMap.get(field);
				List<String> ruleList = new ArrayList<String>();
				List<String> messageList = new ArrayList<String>();
				
				for(int i=0;i<annotationList.size();i++) {
					Map<String, String> validateMap = annotationToString(annotationList.get(i), groupName);
					if(validateMap!=null&&validateMap.get("rule")!=null) {
						ruleList.add(validateMap.get("rule"));
					}
					if(validateMap!=null&&validateMap.get("message")!=null) {
						messageList.add(validateMap.get("message"));
					}
					
				}
				String name = field.getName();
				ChangeListener changeListener = field.getAnnotation(ChangeListener.class);
				if(changeListener!=null) {
					name = changeListener.name();
				}
				if(ruleList.size()>0) {
					rules.append("\t\t"+name+":{"+StringUtil.join(ruleList.toArray(), ",")+"}");
					if(it.hasNext()) {
						rules.append(",\n");
					} else {
						rules.append("\n");
					}
				}
				if(messageList.size()>0) {
					messages.append("\t\t"+name+":{"+StringUtil.join(messageList.toArray(), ",")+"}");
					if(it.hasNext()) {
						messages.append(",\n");
					} else {
						messages.append("\n");
					}
				}
			}
		}
		
		//rule结尾
		rules.append("\t},\n");
		messages.append("\t}\n");
		validate.append(rules);
		validate.append(messages);
		validate.append("});");
		validateStr = validate.toString();
		return validateStr;
	}
	public static Map<String, String> annotationToString(Annotation annotation, String groups) {
		String name = annotation.annotationType().getName();
		Map<String, String> validateMap = new HashMap<String, String>();
		if(NotNull.class.getName().equals(name)) {
			NotNull a = (NotNull) annotation;
			if(!contains(a.groups(), groups)) {
				return null;
			}
			validateMap.put("rule", "required:true");
			validateMap.put("message", "required:\""+getMessage(a.message(), null)+"\"");
			return validateMap;
		} else if(NotBlank.class.getName().equals(name)) {
			NotBlank a = (NotBlank) annotation;
			if(!contains(a.groups(), groups)) {
				return null;
			}
			validateMap.put("rule", "required:true");
			validateMap.put("message", "required:\""+getMessage(a.message(), null)+"\"");
			return validateMap;
		} else if(NotEmpty.class.getName().equals(name)) {
			NotEmpty a = (NotEmpty) annotation;
			if(!contains(a.groups(), groups)) {
				return null;
			}
			validateMap.put("rule", "required:true");
			validateMap.put("message", "required:\""+getMessage(a.message(), null)+"\"");
			return validateMap;
		} else if(Length.class.getName().equals(name)) {
			Length a = (Length) annotation;
			if(!contains(a.groups(), groups)) {
				return null;
			}
			Map<String, String> map = new HashMap<String, String>();
			map.put("min", a.min()+"");
			map.put("max", a.max()+"");
			validateMap.put("rule", "rangelength:["+a.min()+","+a.max()+"]");
			validateMap.put("message", "rangelength:\""+getMessage(a.message(), map)+"\"");
			return validateMap;
		} else if(Size.class.getName().equals(name)) {
			Size a = (Size) annotation;
			if(!contains(a.groups(), groups)) {
				return null;
			}
			Map<String, String> map = new HashMap<String, String>();
			map.put("min", a.min()+"");
			map.put("max", a.max()+"");
			validateMap.put("rule", "rangelength:["+a.min()+","+a.max()+"]");
			validateMap.put("message", "rangelength:\""+getMessage(a.message(), map)+"\"");
			return validateMap;
		} else if(Range.class.getName().equals(name)) {
			Range a = (Range) annotation;
			if(!contains(a.groups(), groups)) {
				return null;
			}
			Map<String, String> map = new HashMap<String, String>();
			map.put("min", a.min()+"");
			map.put("max", a.max()+"");
			validateMap.put("rule", "range:["+a.min()+","+a.max()+"]");
			validateMap.put("message", "range:\""+getMessage(a.message(), map)+"\"");
			return validateMap;
		} else if(Email.class.getName().equals(name)) {
			Email a = (Email) annotation;
			if(!contains(a.groups(), groups)) {
				return null;
			}
			validateMap.put("rule", "email:true");
			validateMap.put("message", "email:\""+getMessage(a.message(), null)+"\"");
			return validateMap;
		} else if(URL.class.getName().equals(name)) {
			URL a = (URL) annotation;
			if(!contains(a.groups(), groups)) {
				return null;
			}
			validateMap.put("rule", "url:true");
			validateMap.put("message", "url:\""+getMessage(a.message(), null)+"\"");
			return validateMap;
		} else if(Min.class.getName().equals(name)) {
			Min a = (Min) annotation;
			if(!contains(a.groups(), groups)) {
				return null;
			}
			Map<String, String> map = new HashMap<String, String>();
			map.put("value", a.value()+"");
			validateMap.put("rule", "min:"+a.value());
			validateMap.put("message", "min:\""+getMessage(a.message(), map)+"\"");
			return validateMap;
		} else if(Max.class.getName().equals(name)) {
			Max a = (Max) annotation;
			if(!contains(a.groups(), groups)) {
				return null;
			}
			Map<String, String> map = new HashMap<String, String>();
			map.put("value", a.value()+"");
			validateMap.put("rule", "max:"+a.value());
			validateMap.put("message", "max:\""+getMessage(a.message(), map)+"\"");
			return validateMap;
		} else if(DecimalMin.class.getName().equals(name)) {
			DecimalMin a = (DecimalMin) annotation;
			if(!contains(a.groups(), groups)) {
				return null;
			}
			String value = "";
			if(a.inclusive()) {
				value = a.value();
			} else {
				value = ""+(new BigDecimal(a.value()).add(new BigDecimal("0.000000000000000000001")));
			}
			Map<String, String> map = new HashMap<String, String>();
			map.put("value",value);
			validateMap.put("rule", "min:"+a.value());
			validateMap.put("message", "min:\""+getMessage(a.message(), map)+"\"");
			return validateMap;
		} else if(DecimalMax.class.getName().equals(name)) {
			DecimalMax a = (DecimalMax) annotation;
			if(!contains(a.groups(), groups)) {
				return null;
			}
			String value = "";
			if(a.inclusive()) {
				value = a.value();
			} else {
				value = ""+(new BigDecimal(a.value()).add(new BigDecimal("0.000000000000000000001")));
			}
			Map<String, String> map = new HashMap<String, String>();
			map.put("value",value);
			validateMap.put("rule", "max:"+a.value());
			validateMap.put("message", "max:\""+getMessage(a.message(), map)+"\"");
			return validateMap;
		//jquery validate没有对应的验证整数位数和小数位数的方法，只能通过自定义实现
		} else if(Digits.class.getName().equals(name)) {
			Digits a = (Digits) annotation;
			if(!contains(a.groups(), groups)) {
				return null;
			}
			Map<String, String> map = new HashMap<String, String>();
			map.put("integer",a.integer()+"");
			map.put("fraction",a.fraction()+"");
			validateMap.put("rule", "number:true");
			validateMap.put("message", "number:\""+getMessage(a.message(), map)+"\"");
			return validateMap;
		//只验证了日期的格式
		} else if(Future.class.getName().equals(name)) {
			Future a = (Future) annotation;
			if(!contains(a.groups(), groups)) {
				return null;
			}
			validateMap.put("rule", "date:true");
			validateMap.put("message", "date:\""+getMessage(a.message(), null)+"\"");
			return validateMap;
		//只验证了日期的格式
		} else if(Past.class.getName().equals(name)) {
			Past a = (Past) annotation;
			if(!contains(a.groups(), groups)) {
				return null;
			}
			validateMap.put("rule", "date:true");
			validateMap.put("message", "date:\""+getMessage(a.message(), null)+"\"");
			return validateMap;
		}
		return null;
	}
	
	private static boolean contains(Class[] clazzs, String groups) {
		String[] groupArray = groups.split(",");
		for(int i=0;i<groupArray.length;i++) {
			for(int j=0;j<clazzs.length;j++) {
				if(clazzs[j].getName().equals(groupArray[i])) {
					return true;
				}
			}
		}
		return false;
	}
	
	private static java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("\\{[^}]*\\}");
	
	public static String getMessage(String messageTemplate, Map<String, String> params) {
		Matcher matcher = pattern.matcher(messageTemplate);
		if(matcher.find()) {
			
			String message = messageSource.getMessage(messageTemplate.replace("{", "").replace("}", ""), null,  new Locale.Builder().setLanguage("zh").setRegion("CN").build());
			matcher = pattern.matcher(message);
			Set<String> set = new HashSet<String>();
			while(matcher.find()) {
				set.add(matcher.group());
			}
			if(set.size()>0) {
				Iterator<String> it = set.iterator();
				while(it.hasNext()) {
					String key = it.next();
					message = message.replaceAll("\\{"+key.replace("{", "").replace("}", "")+"\\}", params.get(key.replace("{", "").replace("}", "")));
				}
				
			}
			return message;
		} else {
			return messageTemplate;
		}
	}
	
	
	

}
